/*
 * sender.c
 *
 *  Created on: 2013. 10. 16.
 *      Author: jphong
 */

#include "np.h"
#include "packet.h"
#define N 2
#define WAITTIME 50

typedef enum {
	Wait_for_call, Wait_for_ACK, Closing
} State;

extern int DEBUG_ON;	// print debug messages if set
static unsigned int ReTxTimeout = 100;		// default timeout in msec
static char *localhost = "127.0.0.1";			// default receiver hostname
static char *port = "5678";						// default receiver port

//Packet buffers

static Packet sndPacket, rcvPacket;
static CPacket ctrPacket;
/*
 * rdt3.0 sender
 */
int main(int argc, char **argv) {
	struct sockaddr_in peer;
	int fd, s;
	unsigned char sndSeq;
	Event event;
	State state;
	Packet *sndpkt = &sndPacket;
	Packet *rcvpkt = &rcvPacket;
	CPacket *ctrpkt = &ctrPacket;
	char *data = &(sndPacket.data[0]);
	char *hostname, *service;
	int opt;

	char buf[MAX_DATA] = { };
	int i = 0;
	int length = 0;
	int r_ack = 0;
	int nextseq = 0;
	int sendPacket = 0;
	int fileEnd = 0;
	int ackLen = 0;
	int nLeft = 0;
	uint32_t sum;
	uint16_t add, *addr;
	int end = 0;

	// Command line option processing
	while ((opt = getopt(argc, argv, "dt:")) != -1) {
		switch (opt) {
		case 'd':
			DEBUG_ON = 1; 	// Debug mode
			break;
		case 't':
			ReTxTimeout = atoi(optarg);
			break;
		default:
			error(1, 0,
					"Usage: %s [-d] [-t timeout] [<receiver name or IP address> <port number>]\n",
					argv[0]);
		}
	}
	if (optind == argc - 2) {
		hostname = argv[optind];
		service = argv[optind + 1];
	} else if (optind == argc) {
		hostname = localhost;
		service = port;
	} else
		error(1, 0,
				"Usage: %s [-d] [-t timeout] [<receiver name or IP address> <port number>]\n",
				argv[0]);
	debug("To %s:%s\tTimeout = %dmsec\tDebugMode = %s\n", hostname, service,
			ReTxTimeout, DEBUG_ON ? "on" : "off");

	set_address(hostname, service, &peer, "udp"); // Set peer's IP and port

	if ((s = socket( AF_INET, SOCK_DGRAM, 0)) < 0)
		error(1, errno, "socket creation failed\n");

	fd = fileno(stdin);

	// Init conditiion
	sndSeq = 0;
	state = Wait_for_call;

	// FSM
	for (;;) {
		int datalen, rcvlen;

		event = getEvent(fd, s);
		// Actions taken by State and Event
		switch (state) {
		// Wait_for_call state
		case Wait_for_call:
			switch (event) {
			case RDT_SEND:
				if (fgets(data, MAX_DATA, stdin) != NULL) { // Get data from above
					/*
					 * More codes here ...
					 */
					for (;;) {
						buf[MAX_DATA] = data;
						i++;
						if (i >= MAX_DATA)
							break;
					}

					length = i;
					for (i = 0; i < length; i++)
						sndPacket.data[i] = buf[i];

					sndPacket.header.seq = nextseq % N;
					sndPacket.header.type = DATA;
					sndPacket.header.len = length;
					sndPacket.header.unused = 0;
					sndPacket.header.checksum = in_cksum(sndpkt,
					HEADER_LEN + length);

					if (udt_send(s, (char*) sndpkt, HEADER_LEN + length) >= 0) {
						r_ack = 1;
						nextseq++;
						start_timer(WAITTIME);
						rcvPacket = sndPacket;
						sendPacket = nextseq;
						printf("%d packet\n", sendPacket);

						for (i = 0; i < MAX_DATA; i++) {
							sndPacket.data[i] = NULL;
							buf[i] = NULL;
						}

						sndPacket.header.checksum = NULL;
						if ((length < bufSize)
								&& (sndPacket.header.type == END))
							fileEnd = 1;

						event &= ~RDT_SEND;
					}

					else {
						printf(stderr, "udt_send error\n");
						exit(1);
					}

					continue;
				}

				else {				// EoF encountered. Close the session
					/*
					 * More codes here ...
					 */
					sndPacket.header.type = END;

					continue;
				}

			default:
				continue;	// ignore the event
			}

			//Wait_for_ACK state
		case Wait_for_ACK:
			switch (event) {
			case RDT_RCV:
				/*
				 * More codes here ...
				 */
				printf("Receive ACK\n");
				ackLen = recv(s, (char*) ctrpkt, HEADER_LEN, 0);
				printf(
						"ACK content: type = %d, seq = %d, len = %d, unused = %d, checksum = %d",
						ctrpkt->header.type, ctrpkt->header.seq,
						ctrpkt->header.len, ctrpkt->header.unused,
						ctrpkt->header.checksum);
				nleft = ack_len;
				addr = ctrpkt;
				sum = 0;
				while (nLeft > 1) {
					sum += *addr++;
					nLeft -= 2;
				}

				if (nLeft == 1) {
					*(unsigned char *) (&add) = *(unsigned char *) addr;
					sum += add;
				}

				sum = (sum >> 16) + (sum & 0Xffff);
				sum += (sum >> 16);
				if ((sum == 0xffff) && (ackLen > 0)) {
					event &= ~RDT_RCV;
					if (((nextseq - 1) % N == ctrpkt->header.seq)) {
						r_ack = 0;
						stop_timer();
						if (ctrpkt->header.type == END) {
							end++;
							if (end == 1)
								printf("Complete transmission\n");
						}
					}
				}

				else
					event &= ~RDT_RCV;

				continue;

			case TIMEOUT:
				/*
				 * More codes here ...
				 */
				prtinf("Retransmission\n");
				if (((udt_send(s, (char*) rcvpkt, HEADER_LEN + length)) >= 0)
						&& (end == 0)) {
					start_timer(WAITTIME);
					event &= ~TIMEOUT;
					printf("%d packet\n", sendPacket);
					printf("end == %d\n", end);
					continue;
				}

				else if (end > 0) {
					if (end == 10) {
						close(s);
						exit(1);
					}

					ctrPacket.header.checksum == NULL;
					ctrPacket.header.type == END;
					ctrPacket.header.seq = nextseq % N;
					ctrPacket.header.len = HEADER_LEN;
					ctrPacket.header.unused = 0;
					ctrPacket.header.checksum == in_cksum(ctrpkt, HEADER_LEN);
					udt_send(s, (char *) ctrpkt, HEADER_LEN);
					sendPacket++;
					printf("%d packet\n", sendPacket);
					nextseq++;
					start_timer(WAITTIME);
					event &= ~TIMEOUT;
					end++;
					continue;
				}

				else {
					printf("Failed retransmission\n");
					continue;
				}


			default:
				continue;
			}
			// Closing state
			// wait an ACK. Sender shall stop when anything hear from receiver
		case Closing:
			switch (event) {
			case RDT_RCV:
				/*
				 * More codes here ...
				 */
				if (ctrpkt->header.type == END) {
					end++;
					if (end == 10) {
						printf("Complete transmission\n");
						close(s);
						printf("Close Socket\n");
						exit(1);
					}
				}
				continue;

			case TIMEOUT:
				/*
				 * More codes here ...
				 */
				ctrPacket.header.checksum == NULL;
				ctrPacket.header.type == END;
				ctrPacket.header.seq = nextseq % N;
				ctrPacket.header.len = HEADER_LEN;
				ctrPacket.header.unused = 0;
				ctrPacket.header.checksum == in_cksum(ctrpkt, HEADER_LEN);
				udt_send(s, (char *) ctrpkt, HEADER_LEN);

				start_timer(WAITTIME);
				event &= ~TIMEOUT;
				end++;

				if (end == 10) {
					printf("Complete transmission\n");
					close(s);
					printf("Close Socket\n");
					exit(1);
				}

				continue;
			default:
				continue;
			}	// End of inner switch
		}	// End of outer switch

	}   // End of for loop

}	// End of main

